﻿#region File and License Information
/*
<File>
	<License Type="BSD">
		Copyright © 2009 - 2012, Outcoder. All rights reserved.
	
		This file is part of Calcium (http://calciumsdk.net).

		Redistribution and use in source and binary forms, with or without
		modification, are permitted provided that the following conditions are met:
			* Redistributions of source code must retain the above copyright
			  notice, this list of conditions and the following disclaimer.
			* Redistributions in binary form must reproduce the above copyright
			  notice, this list of conditions and the following disclaimer in the
			  documentation and/or other materials provided with the distribution.
			* Neither the name of the <organization> nor the
			  names of its contributors may be used to endorse or promote products
			  derived from this software without specific prior written permission.

		THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
		ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
		WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
		DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
		DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
		(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
		LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
		ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
		(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
		SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
	</License>
	<Owner Name="Daniel Vaughan" Email="danielvaughan@outcoder.com" />
	<CreationDate>2010-03-03 14:00:44Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;

using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Prism.Regions.Behaviors;

namespace Outcoder.Calcium.Modularity
{
	public class CustomRegionManagerRegistrationBehavior : RegionBehavior, IHostAwareRegionBehavior
    {
        WeakReference attachedRegionManagerWeakReference;
        DependencyObject hostControl;

        /// <summary>
        /// Initializes a new instance of <see cref="RegionManagerRegistrationBehavior"/>.
        /// </summary>
		public CustomRegionManagerRegistrationBehavior()
        {
            this.RegionManagerAccessor = new DefaultRegionManagerAccessor();
        }

        /// <summary>
        /// Provides an abstraction on top of the RegionManager static members.
        /// </summary>
        public IRegionManagerAccessor RegionManagerAccessor { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
        /// </summary>
        /// <value>A <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
        /// This is usually a <see cref="FrameworkElement"/> that is part of the tree.</value>
        /// <exception cref="InvalidOperationException">When this member is set after the <see cref="IRegionBehavior.Attach"/> method has being called.</exception>
        public DependencyObject HostControl
        {
            get
            {
                return hostControl;
            }
            set
            {
                if (IsAttached)
                {
                    throw new InvalidOperationException("Host cannot be set after attach.");
                }
                hostControl = value;
            }
        }

        /// <summary>
        /// When the <see cref="IRegion"/> has a name assigned, the behavior will start monitoring 
        /// the ancestor controls in the element tree
        /// to look for an <see cref="IRegionManager"/> where to register the region in.
        /// </summary>
        protected override void OnAttach()
        {
            if (string.IsNullOrEmpty(this.Region.Name))
            {
                Region.PropertyChanged += Region_PropertyChanged;
            }
            else
            {
                StartMonitoringRegionManager();
            }
        }

        private void Region_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Name" && !string.IsNullOrEmpty(this.Region.Name))
            {
                this.Region.PropertyChanged -= this.Region_PropertyChanged;
                this.StartMonitoringRegionManager();
            }
        }

        private void StartMonitoringRegionManager()
        {
            this.RegionManagerAccessor.UpdatingRegions += this.OnUpdatingRegions;
            this.TryRegisterRegion();
        }

        private void TryRegisterRegion()
        {
            DependencyObject targetElement = this.HostControl;
            if (targetElement.CheckAccess())
            {
                IRegionManager regionManager = this.FindRegionManager(targetElement);

                IRegionManager attachedRegionManager = this.GetAttachedRegionManager();

                if (regionManager != attachedRegionManager)
                {
                    if (attachedRegionManager != null)
                    {
                        this.attachedRegionManagerWeakReference = null;
                        attachedRegionManager.Regions.Remove(this.Region.Name);
                    }

                    if (regionManager != null)
                    {
                        this.attachedRegionManagerWeakReference = new WeakReference(regionManager);

                        regionManager.Regions.Add(this.Region);
                    }
                }
            }
        }

        /// <summary>
        /// This event handler gets called when a RegionManager is requering the instances of a region to be registered if they are not already.
        /// <remarks>Although this is a public method to support Weak Delegates in Silverlight, it should not be called by the user.</remarks>
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The arguments.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", Justification = "This has to be public in order to work with weak references in partial trust or Silverlight environments.")]
        public void OnUpdatingRegions(object sender, EventArgs e)
        {
            this.TryRegisterRegion();
        }

        private IRegionManager FindRegionManager(DependencyObject dependencyObject)
        {
            var regionmanager = this.RegionManagerAccessor.GetRegionManager(dependencyObject);
            if (regionmanager != null)
            {
                return regionmanager;
            }

            DependencyObject parent = null;
#if SILVERLIGHT
            //parent = System.Windows.Media.VisualTreeHelper.GetParent(dependencyObject);
                    FrameworkElement frameworkElement = dependencyObject as FrameworkElement;
                    if (frameworkElement != null)
                    {
                        parent = frameworkElement.Parent;
                    }
#else
            parent = LogicalTreeHelper.GetParent(dependencyObject);

			#region Customized for ControlTemplates [DV]
			/* [DV] Customized to allow styling. 
			 * Top level ancestor will not have a parent if it is in a ControlTemplate. */
			if (parent == null)
			{
				var temp = dependencyObject as FrameworkElement;
				if (temp != null)
				{
					parent = temp.TemplatedParent;
				}
			}
			#endregion

#endif
			if (parent != null)
            {
                return this.FindRegionManager(parent);
            }

            return null;
        }

        IRegionManager GetAttachedRegionManager()
        {
            if (this.attachedRegionManagerWeakReference != null)
            {
                return this.attachedRegionManagerWeakReference.Target as IRegionManager;
            }

            return null;
        }
    }

	class DefaultRegionManagerAccessor : IRegionManagerAccessor
	{
		/// <summary>
		/// Notification used by attached behaviors to update the region managers appropriatelly if needed to.
		/// </summary>
		/// <remarks>This event uses weak references to the event handler to prevent this static event of keeping the
		/// target element longer than expected. For security reasons, to use weak delegates in Silverlight you must provide
		/// a delegate that is available in the public API of the class (no private or anonymous delegates allowed).</remarks>
		public event EventHandler UpdatingRegions
		{
			add
			{
				RegionManager.UpdatingRegions += value;
			}
			remove
			{
				RegionManager.UpdatingRegions -= value;
			}
		}

		/// <summary>
		/// Gets the value for the RegionName attached property.
		/// </summary>
		/// <param name="element">The object to adapt. This is typically a container (i.e a control).</param>
		/// <returns>The name of the region that should be created when 
		/// the RegionManager is also set in this element.</returns>
		public string GetRegionName(DependencyObject element)
		{
			return element.GetValue(RegionManager.RegionNameProperty) as string;
		}

		/// <summary>
		/// Gets the value of the RegionName attached property.
		/// </summary>
		/// <param name="element">The target element.</param>
		/// <returns>The <see cref="IRegionManager"/> attached to the <paramref name="element"/> element.</returns>
		public IRegionManager GetRegionManager(DependencyObject element)
		{
			return element.GetValue(RegionManager.RegionManagerProperty) as IRegionManager;
		}
	}
}
